﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Globalization;
using ConfigManager.Core.Instructions;

namespace ConfigManager.Core
{
    public class XmlInstructionParser
    {
        /// <summary>
        /// Header used to identify an instruction as a ConfigManager.Net instruction.
        /// </summary>
        private const string HEADER = "#ConfigManager.Net#";

        /// <summary>
        /// Parses the specified XML document for instructions.
        /// </summary>
        /// <param name="xmlDoc">The XML document.</param>
        /// <param name="properties">The properties.</param>
        /// <returns>All XML Instructions in the document,</returns>
        public static XmlInstruction[] Parse(XmlDocument xmlDoc, IDictionary<string, string> properties)
        {
            List<XmlInstruction> instructions = new List<XmlInstruction>();
            XmlNodeList list = xmlDoc.SelectNodes("//comment()");
            
            OpenSectionInstruction currentOpenSection = null;
            foreach (XmlNode node in list)
            {
                string comment = node.Value.Trim();
                if(comment.StartsWith(HEADER, true, CultureInfo.CurrentCulture) == false)
                    continue;

                comment = Regex.Replace(comment, HEADER, "", RegexOptions.IgnoreCase).Trim();
                foreach (string key in properties.Keys)
                    comment = Regex.Replace(comment, Regex.Escape("$(" + key + ")"), properties[key], RegexOptions.IgnoreCase);

                string instName = comment.Substring(0, Regex.Replace(comment, "\n", " ").IndexOf(" "));
                instName = instName.Trim().ToUpper();
                comment = Regex.Replace(comment, instName, "", RegexOptions.IgnoreCase).Trim();

                XmlInstruction target;
                switch (instName)
                {
                    case "ENDOPENSECTION":
                        currentOpenSection.EndNode = node;
                        currentOpenSection = (OpenSectionInstruction)currentOpenSection.ParentInstruction;
                        continue;

                    case "EMBEDDEDSECTION":
                        string[] lines = Regex.Split(comment, Environment.NewLine);
                        target = new EmbeddedSectionInstruction(xmlDoc, node, ParseParameters(lines[0]));

                        StringBuilder builder = new StringBuilder();
                        for (int i = 1; i < lines.Length; i++)
                            builder.AppendLine(lines[i]);
                        ((EmbeddedSectionInstruction)target).Data = builder.ToString();
                        break;

                    case "EXTERNALSECTION":
                        target = new ExternalSectionInstruction(xmlDoc, node, ParseParameters(comment));
                        break;

                    case "OPENSECTION":
                        target = new OpenSectionInstruction(xmlDoc, node, ParseParameters(comment));
                        break;

                    case "SETVALUE":
                        target = new SetValueInstruction(xmlDoc, node, ParseParameters(comment));
                        break;

                    default:
                        throw new ParsingException(String.Format(ResourceStrings.InvalidInstructionName, instName));
                }
               
                if (currentOpenSection != null)
                {
                    currentOpenSection.ChildInstructions.Add(target);
                    target.ParentInstruction = currentOpenSection.ParentInstruction;
                }
                else
                    instructions.Add(target);
                

                if (target is OpenSectionInstruction)
                    currentOpenSection = (OpenSectionInstruction) target;
            }

            return instructions.ToArray();
        }

        /// <summary>
        /// Parses the instruction parameters.
        /// </summary>
        /// <param name="instHeader">The inst header.</param>
        /// <returns>All parameters for the instuction.</returns>
        private static IDictionary<string, string> ParseParameters(string instHeader)
        {
            string pattern = String.Format(@"(?<name>.*?)={0}(?<val>.*?){0}", Regex.Escape("\""));
            MatchCollection matches = Regex.Matches(instHeader, pattern);

            IDictionary<string, string> parameters = new Dictionary<string, string>();
            foreach (Match match in matches)
                parameters.Add(match.Groups["name"].Value.Trim(), match.Groups["val"].Value);
            
            return parameters;
        }

    }
}
